package org.OpenGeoPortal.Download;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.HashSet;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;

public class DownloadPackager extends AbstractDownloadHandler implements
		DownloadHandler {
	private MetadataRetriever metadataRetriever;

	
	public void setDownloadConfigRetriever(DownloadConfigRetriever downloadConfigRetriever){
		this.downloadConfigRetriever = downloadConfigRetriever;
	}
	
	public void setLayerInfoRetriever(LayerInfoRetriever layerInfoRetriever){
		this.layerInfoRetriever = layerInfoRetriever;
	}
	
	public void setMetadataRetriever(MetadataRetriever metadataRetriever){
		this.metadataRetriever = metadataRetriever;
	}
	
	String filterFileName(String layerName){
		//replace periods with underscores
		layerName = layerName.replace(".", "_");
		return layerName;
	}
	
	@Override
	void doWork() {
		try {
		    for (RequestedLayer layer : this.layers) {
		    	if (layer.getStatus() == LayerStatus.DOWNLOAD_SUCCESS){
		    		if (!layer.metadata){
		    			//get metadata for this layer, add the resulting xml file to the file list
		    			File xmlFile;
		    			if (layer.isVector()&&(!layer.requestedFormat.equals("kmz"))){
		    				xmlFile = new File(this.getDownloadDirectory(), this.filterFileName(layer.name) + ".shp.xml");
		    			} else {
		    				xmlFile = new File(this.getDownloadDirectory(), this.filterFileName(layer.name) + ".xml");
		    			}

		    			layer.downloadedFiles.add(this.metadataRetriever.getXMLFile(layer.name, xmlFile));
		    		}
		    	}
		    }
			this.addFilesToZipArchive();
		} catch (Exception e) {
			System.out.println("File Download Error(doWork): " + e.getMessage());
			this.setLayerLink("Error downloading files");
			return;
		}
		this.setLayerLink(this.downloadDirectoryName + "/" + this.zipArchive.getName());
	}
	
	void setLayerLink(String layerLink){
		this.layerLink = layerLink;
	}
	
	String getLayerLink(){
		return this.layerLink;
	}
	
	private File createZipArchive() throws Exception {
		File zipFile;
		do {
			String zipFileName = "OGP" + Math.round(Math.random() * 10000) + ".zip";
			zipFile = new File(this.getDownloadDirectory(), zipFileName);
		} while (zipFile.exists());
		this.zipArchive = zipFile;
		return zipFile;
	}
	
	private void addFilesToZipArchive()
	throws Exception
	{
	    int zipFileCounter = 0;
	    Set<File> filesToPackage = new HashSet<File>();
	    for (RequestedLayer layer : this.layers) {
	    	System.out.println(layer.name);
	    	System.out.println(layer.getStatus());
	    	System.out.println(layer.getDisposition());
	    	 
	    	if ((layer.getStatus() == LayerStatus.DOWNLOAD_SUCCESS)&&
	    			(layer.getDisposition() == LayerDisposition.DOWNLOADED_LOCALLY)){
	    		filesToPackage.addAll(layer.downloadedFiles);
	    	}
	    }
	 
	    if (filesToPackage.size() < 1){
	    	throw new Exception("no files found");
	    }

		System.out.println("packaging files...");
		byte[] buffer = new byte[1024 * 1024];
		long startTime = System.currentTimeMillis();
	    
		ZipOutputStream newZipStream = new ZipOutputStream(new FileOutputStream (this.createZipArchive()));
	    for (File currentFile : filesToPackage){
			FileInputStream currentFileStream = new FileInputStream(currentFile);
	    	zipFileCounter++;
	    	if (!currentFile.getName().contains(".zip")){
	    		//add this uncompressed file to the archive
	    		int bytesRead;
	        	String entryName = currentFile.getName();
	        	ZipEntry zipEntry = new ZipEntry(entryName);
	        	newZipStream.putNextEntry(zipEntry);
	            while ((bytesRead = currentFileStream.read(buffer))!= -1) {
	                newZipStream.write(buffer, 0, bytesRead);
	            }
	    	} else {
	    		ZipInputStream currentZipStream = new ZipInputStream(currentFileStream);
	    		ZipEntry currentEntry;
	        	while ((currentEntry = currentZipStream.getNextEntry()) != null) {
	        		String entryName = currentEntry.getName();
		        	ZipEntry zipEntry = new ZipEntry(entryName);
		        	try {
		        		newZipStream.putNextEntry(zipEntry);
		        	} catch (ZipException e){
		        		//duplicate names should never happen.
			        	entryName = Math.round(Math.random() * 10000) + "_" + entryName;
			        	ZipEntry zipEntry2 = new ZipEntry(entryName);
		        		newZipStream.putNextEntry(zipEntry2);
		        	}
	            	int bytesRead;
	            	while ((bytesRead = currentZipStream.read(buffer))!= -1) {
	                	newZipStream.write(buffer, 0, bytesRead);
	           	 	}
	        	}
	        	currentZipStream.close();
	    	}	
	        currentFile.delete();
    	}

	    if (zipFileCounter > 0){
	     	newZipStream.close();
	    }

		long endTime = System.currentTimeMillis();
		System.out.println(zipFileCounter + " file(s) zipped in " + (endTime - startTime) + " milliseconds.");
	}

	@SuppressWarnings("unchecked")
	@Override
	public String getJsonResponse() {
		//return a status message for each requested layer
		JSONArray successArray = new JSONArray();
		JSONArray failureArray = new JSONArray();

	    for (RequestedLayer layer : this.layers) {
	    	//System.out.println(layer.name);
    		JSONObject layerInfo = new JSONObject();
    		layerInfo.put("layerId", layer.id);
    		layerInfo.put("institution", layer.institution);
    		layerInfo.put("title", layer.title);
    		layerInfo.put("disposition", layer.getDisposition().name());
    		layerInfo.put("message", layer.getStatus().message());
    		
	    	if (layer.getStatus() == LayerStatus.DOWNLOAD_SUCCESS){
	    		successArray.add(layerInfo);
	    	} else {
	    		failureArray.add(layerInfo);
	    	}
	    }

	    JSONObject responseObject = new JSONObject();
		try {
			if (this.zipArchive.exists()){
				responseObject.put("packageLink", this.getLayerLink());
			}
		} catch (Exception e){}
	    responseObject.put("succeeded", successArray);
	    responseObject.put("failed", failureArray);

		return responseObject.toJSONString();
	}

}
